package org.oy.sealogger.ds.nmea;

import java.io.IOException;
import java.io.InputStream;

import org.oy.sealogger.common.Config;
import org.oy.sealogger.common.MessageQueue;
import org.oy.sealogger.common.Messages;
import org.oy.sealogger.device.Device;
import org.oy.sealogger.device.DeviceException;
import org.oy.sealogger.device.SerialDevice;
import org.oy.sealogger.ds.FilteringDataSource;

/**
 * Abstract superclass for NMEA0138 data sources i.e. GPS devices
 * plugged in different ports. Objects of this class should be constructed using the factory method
 * in DataSourceFactory class.
 * @see org.oy.sealogger.ds.DataSourceFactory
 * @author riku
 *
 */
public class NMEA0183DataSource extends FilteringDataSource {

	public static final int NMEA_LINE_MAX_LENGTH = 90;
	public static final int NMEA_LINE_MIN_LENGTH = 15; // $GPRMC,,,,,,, etc.
	public static final int NMEA_FILTER_LENGTH = 6; // e.g. $GPGSA
	
	private static final int RETRY_TIMES = 32;
	private static final int BUF_SIZE = 90;
	
	private MessageQueue mq = MessageQueue.getInstance();
	
	/**
	 * Constructs a new NMEA0183DataSource
	 * @param device
	 */
	public NMEA0183DataSource(Device device, boolean terminateOnError) {
		super(device, terminateOnError);
		
		if(device instanceof SerialDevice)
			((SerialDevice)device).setPortSpeed(SerialDevice.PortSpeed.B4800);
		
		if(device instanceof Runnable) {
			Thread t = new Thread((Runnable)device);
			t.start();
		}
	}
	
	public void run() {
		
		InputStream in = null;
		boolean interrupted = false;
		int tries = RETRY_TIMES;
		char[] buffer = new char[BUF_SIZE];
		int i = 0;
		
		while(!Thread.interrupted()) {
			
			try {
				mq.info(device.getDeviceName(), Messages.getMessage("info.connecting"));
				
				device.connect();
				in = device.getInputStream();
				interrupted = false;
				tries = RETRY_TIMES;
				
				if(in.available() <= 0) {
					Thread.sleep(Config.DEVICE_DATA_WAIT_TIME); // Wait for some data to appear
				}
				
				mq.info(device.getDeviceName(), Messages.getMessage("info.connected"));
				
			} catch(DeviceException e) {
				log.error(e);
				interrupted = true;
			} catch(IOException e) {
				log.error(e);
				interrupted = true;
			} catch(InterruptedException e) {
				log.error(e);
			}
						
			while(!interrupted && tries > 0) {
								
				try {
					
					int c = in.read();
					
					if(c < 0) {
						--tries;
						continue;
					}
					
					if(c > 255 || (i == 0 && ((char)c) != '$') || c == 13) continue;
					
					if(c != 10)
						buffer[i++] = (char)c;
					
					if(c == 10 || i == BUF_SIZE) {
						String nmea = new String(buffer, 0, i);
						String code = nmea.substring(0, NMEA_FILTER_LENGTH);
						
						if(filters.contains(code)) {
							//log.debug(nmea);
							deliver(nmea);
						}
						
						i = 0;
					}
					
				} catch(IOException e) {
					log.error(e);
					interrupted = true;
				}
			}
			
			try {
				log.debug(Messages.getMessage("error.connection_lost"));
				device.disconnect();
				
				if(!terminateOnError) {
					Thread.sleep(Config.getPropertyAsInt("device.reconnect_interval"));
				} else {
					Thread.currentThread().interrupt();
				}
				
			} catch(DeviceException e) {
				log.error(e);
			} catch(InterruptedException e) {
				log.error(e);
			}
		}
		
		log.debug("NMEA0183DataSource thread terminated");
		
		if(device.isConnected())
			try {
				device.disconnect();
			} catch (DeviceException e) {
				log.error(e);
			}
	}
	
	@Override
	public State getState() {
		return device.isConnected() ? State.OK : State.ERROR;
	}
}
